﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using NHibernate;
using NHibernate.Linq;
using Nvigorate.Extensions;
using StructureMap;

namespace Nvigorate.Repository
{
    public class Repository<TRole> : IRepository<TRole>, IDisposable
        where TRole : class
    {
        private ISessionManager _sessionManager;
        protected ISessionManager SessionManager { get { return _sessionManager; } }

        protected virtual ISession Session
        {
            get { return SessionManager.GetCurrentSession(); }
        }

        protected virtual IQueryable<TModel> CreateQuery<TModel>()
            where TModel : TRole
        {
            var nHibernateQueryable = Session.Linq<TModel>();
            var queryable = ApplyFetchingStategy(nHibernateQueryable);

            return from model in queryable
                   select model;
        }

        protected virtual IQueryable<TModel> ApplyFetchingStategy<TModel>(INHibernateQueryable<TModel> queryable)
            where TModel : TRole
        {
            var fetchingStrategy = GetFetchingStrategy<TModel>();
            return fetchingStrategy.ApplyTo(queryable);
        }

        protected virtual FetchingStrategy<TRole>.For<TModel> GetFetchingStrategy<TModel>()
            where TModel : TRole
        {
            return ObjectFactory.GetInstance<FetchingStrategy<TRole>.For<TModel>>();
        }

        public virtual void Delete<TModel, TIdProperty>(Expression<Func<TModel, TIdProperty>> idExpression, TIdProperty id)
            where TModel : TRole
        {
            var model = Get(idExpression, id);
            if(model != null)
            {
                Session.Delete(model);
            }
        }

        public virtual void Delete<TModel>(SearchCriteria<TModel> criteria)
            where TModel : TRole
        {
            var matching = Find(criteria);
            if(matching.Count() > 0)
            {
                matching.ForEach(Session.Delete);
            }
        }

        public virtual IList<TRole> Find<TModel>(SearchCriteria<TModel> criteria)
            where TModel : TRole
        {
            return criteria.Filter(CreateQuery<TModel>()).Cast<TRole>().ToList();
        }

        public virtual IList<TRole> Find<TModel>(SearchCriteria<TModel> criteria, int page, int pageSize)
            where TModel : TRole
        {
            return criteria.Filter(CreateQuery<TModel>())
                .Skip((page - 1) * pageSize)
                .Take(pageSize)
                .Cast<TRole>()
                .ToList();
        }

        public virtual TRole FindOne<TModel>(SearchCriteria<TModel> criteria)
            where TModel : TRole
        {
            return criteria.Filter(CreateQuery<TModel>()).Take(1).FirstOrDefault();
        }

        public virtual TRole Get<TModel, TIdProperty>(Expression<Func<TModel, TIdProperty>> idExpression, TIdProperty id)
            where TModel : TRole
        {
            var parameter = Expression.Parameter(typeof (TModel), "model");
            var comparison = Expression.Lambda<Func<TModel, bool>>(
                Expression.Equal(idExpression.Body, Expression.Constant(id)),
                parameter
                );

            var criteria = new SearchCriteria<TModel>()
                .Add(comparison);
            return FindOne(criteria);
        }

        public virtual IList<TRole> GetAll<TModel>()
            where TModel : TRole
        {
            return CreateQuery<TModel>().Cast<TRole>().ToList();
        }

        public virtual IList<TRole> GetAll<TModel>(int page, int pageSize)
            where TModel : TRole
        {
            return CreateQuery<TModel>()
                .Skip((page - 1)*pageSize)
                .Take(pageSize)
                .Cast<TRole>()
                .ToList();
        }

        public int GetCount<TModel>(SearchCriteria<TModel> criteria)
            where TModel : TRole
        {
            return criteria.Filter(CreateQuery<TModel>()).Count();
        }

        public int GetTotal<TModel>()
            where TModel : TRole
        {
            return CreateQuery<TModel>().Count();
        }

        public virtual void Save<TModel>(TModel model)
            where TModel : TRole
        {
            var fresh = default(TModel); //Get<TRole>(model.Id);
            if(fresh == null)
            {
                Session.Save(model);
            }
            else
            {
                Session.Update(model);
            }
        }

        public virtual void Save<TModel>(IEnumerable<TModel> models)
            where TModel : TRole
        {
            models.ForEach(Save);
        }

        public Repository(ISessionManager sessionManager)
        {
            _sessionManager = sessionManager;
        }

        #region Implementation of IDisposable

        public void Dispose()
        {
        }

        #endregion
    }
}
